
系列:30 天打造企業級 PLM|面向:前端|素材:前端狀態切片、React Query、
MenuService
企業系統前端的狀態爆炸來得很快:登入者與權限、當前表單、當前品項、metadata 快取、Modal 開關、主題、語系、搜尋結果追蹤。全塞一個 store 是災難的開始,散落各元件的 useState 是另一種災難。今天講 Mini-PLM 前端的核心:Zustand 切片與 server-state cache。
mp_role_folder),但選單只負責導覽與呈現,不能成為前端授權來源以前 Oracle Agile PLM 的 Web Client 是典型的 Multi-Page Application (MPA)。
在那個 Struts + JSP 的年代:
HttpSession,在多視窗或多 Tab 操作時極易互相踩踏覆蓋。Mini-PLM 採用現代 React 19 SPA 架構,透過 Zustand 進行細緻的領域狀態切片,將互動與狀態響應收斂在前端瀏覽器中。
三個理由。樣板代碼量:一個 store 十幾行,沒有 action type 與 dispatcher 的儀式。學習曲線:新人半天上手。選擇性訂閱天然內建:useThemeStore((s) => s.color) 只在 color 變時重渲染。放棄的是 Redux DevTools 的時間旅行與嚴格的單向資料流紀律,對這個團隊規模,換得起。
伺服器狀態另外走 TanStack Query。API 資料的快取、重抓、失效是 Query 的事,Zustand 只放純前端狀態。兩者的界線就一句:這個狀態有沒有伺服器上的真相來源?有就 Query,沒有就 Zustand。

| Store | 職責 |
|---|---|
useAuthStore |
JWT、登入者、權限 |
useFormStore / useItemStore |
當前表單/品項的操作上下文 |
useMetaStore |
Form/Item Type 與欄位定義(全域快取,Day 8 的 meta 來源) |
useRefDataStore |
參考資料快取(動態列表、外部 lookup) |
useUIStore / useThemeStore / useLocaleStore |
Modal 開關/主題色/語系 |
useResultTrackStore / useSearchCacheStore |
搜尋結果追蹤/條件快取 |
按領域切而不是按頁面切,因為頁面會重組(Modal 變頁、頁拆成兩頁),領域不會。切片的判斷句是「這個狀態屬於哪個業務概念」,不是「這個狀態被哪個頁面用」。store 之間依賴保持單向,業務 store 可讀 authStore,反向禁止,避免初始化順序地獄。
選單仍由 mp_menu 提供導覽資料,後端 MenuService 使用 Spring Cache;本文聚焦前端如何管理這些 server state,不展開路由實作。
動態選單還有一個部署面的配套:啟動時自我補齊。系統內建的管理頁(Manager Import、Config Migration 這類)若要求每個環境部署後都手動去 ConfigMenu 建一列,遲早漏。AdminMenuBootstrapService 在 ApplicationReadyEvent 時依 link upsert 這些固定入口:
@EventListener(ApplicationReadyEvent.class)
@Transactional // 交易必須放在事件入口方法,避免 self-invocation 使 @Transactional 失效
@CacheEvict(value = { "menuCache", "userCache" }, allEntries = true)
public void initAfterApplicationReady() {
init();
}
「使用者自建的選單走 ConfigMenu、系統內建的入口走啟動 upsert」,兩條路徑各管各的,環境重建不再需要一張手動建選單的 checklist。
優先快取「讀多寫少、可由事件失效」的 server state:Form/Item Type 與欄位 metadata、使用者選單與 privilege、參考資料(使用者、群組、workflow)、最近瀏覽與 bookmarks。這些資料統一放 TanStack Query,以 query key 區分使用者、物件 ID、分頁、排序與搜尋條件;mutation 或 SSE 事件到達後精準 invalidateQueries。
不建議把 JWT、表單草稿、權限結果或 Query cache 直接持久化到 localStorage:前兩者有安全與跨帳號殘留風險,後兩者容易展示過期資料。登出時清空 QueryClient;跨分頁若要同步,只同步「失效通知」,不要同步敏感 payload。

reset(),logout 流程逐一呼叫AdminMenuBootstrapService 早期用 findByLink 查既有選單,某環境歷史資料同一個 link 有兩列,非唯一結果直接拋例外——啟動流程掛在選單補齊上,整個後端起不來。修法是改成清單查詢容忍重複:保留第一筆更新、其餘停用並留 log,bootstrap 從「遇髒資料就死」變成「順手自我修復」。啟動期程式碼的容錯標準要比執行期更嚴,因為它一失敗就是全系統陪葬狀態按領域切片,伺服器狀態歸 Query、純前端歸 Zustand;cache 以 query key、事件失效與登出清除維持正確性。明日 Day 18:antd/ProTable 實戰陷阱與 i18n。